/*
 * Peripheral Simulator (PSim)
 *
 * Copyright (c) 2008 Avadh Patel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "psim_lib.h"
//#include "psim.h"
#include "sim_io_mem.h"
#include "exec-all.h"
//#include "vl.h"

//#include <stdio.h>
#include <stdlib.h>

#define NUM_MAX_MESSAGES 128

//Flag:simulate
static uint8_t simulate=0;
static uint64_t simulated_op_counter=0;
static uint64_t sim_clock;

//List of callback functions 
SimCB *sim_cb;

//no_return flag : this flag indicate that simulations can't return to
//executing cpu instructions till this flag is cleared - this is used
//to simulate the timing delay in out/in cpu instructions 
static uint8_t no_return; 

void sim_set_no_return()
{
  no_return = 1;
}

void sim_reset_no_return()
{
  no_return = 0;
}

int sim_get_no_return()
{
  return no_return;
}

uint64_t sim_get_clock()
{
  return sim_clock;
}

static inline void sim_reset_op_counter()
{
  simulated_op_counter = 0;
}

void sim_enable()
{
  simulate = 1;
  //reset the simulated_op_counter
  sim_reset_op_counter();
  //At now we reset the sim_clock everytime we restart the simulation..
  //In future we can change this to include pause or fasw fwd in simulation mode...
  sim_clock = 0;

  tb_flush(first_cpu);
  sim_cb = NULL;
}

void sim_disable()
{
  simulate = 0;
}

int is_sim_enable()
{
  return simulate;
}


void sim_do_callback()
{
  //First increment the clock
//  sim_clock++;

  //now check if any pending callback for this cycle
  do_callback:
    while(sim_cb != NULL) {
      if(sim_cb->time == sim_clock) {
	sim_cb->func(sim_cb->arg_data);
	void* fr = sim_cb;
	sim_cb = sim_cb->next;
	free(fr);
	continue;
      }
      sim_clock++;
    }

//    if(sim_cb != NULL && sim_cb->time == sim_clock)
//    {
//      //call this CB
//      sim_cb->func(sim_cb->arg_data);
//      sim_cb = sim_cb->next;
//      goto do_callback;
//    }
//
//  if(no_return)
//  {
//    //increment the clock and goto do_callback
//    sim_clock++;
//    goto do_callback;
//  }
}

//Simulator's Callback function Handeling 
void sim_insert_cb(SimCBFunc* _func, Message* _data, uint8_t delta)
{
  uint64_t _time = sim_get_clock() + delta;
  SimCB *temp_cb = (SimCB*)malloc(sizeof(SimCB));
  temp_cb->func = _func;
  temp_cb->arg_data = _data;
  temp_cb->time = _time;
  temp_cb->next = NULL;

  if(_func == NULL) {
    exit(-1);
  }
//  printf("Inserting new message from %s with delay %d\n",
//      ((SimDevice**)_data->from)->name, delta);

  //Check if there is any pending callback
  if(sim_cb == NULL)
  {
    //no pending callback so insert this one..
    sim_cb = temp_cb;
    return;
  }
  //Check if we need to insert at first place
  if(sim_cb->time > temp_cb->time)
  {
    temp_cb->next = sim_cb;
    sim_cb = temp_cb;
    return;
  }
  //Start searching the sim_cb and find the place to insert
  //new SimCB, the searching is in linear time... 
  SimCB *prev = sim_cb;
  SimCB *iter;
  do
  {
    iter = prev->next;
    if(iter == NULL)
    {
      //insert at the end
      prev->next = temp_cb;
      return;
    }
    if(iter->time > temp_cb->time)
    {
      //insert here
      temp_cb->next = iter;
      prev->next = temp_cb;
      return;
    }
    prev = iter;
  }while(1);

  return;
}

//Run the callback functions

//This function will be called after every instruction executed in CPU
void sim_inc_op_counter()
{
  simulated_op_counter++;
  sim_clock++;
  sim_do_callback();
}

uint64_t sim_get_op_counter()
{
  return simulated_op_counter;
}

//Register Basic simulation stats counters
void sim_reg_stats(struct stat_sdb_t* sdb)
{
  stat_reg_counter(sdb, "sim_num_instructions",  
      "Total number of instruction executed",
      &simulated_op_counter, simulated_op_counter, NULL);
  
  stat_reg_counter(sdb, "sim_clock",
      "Simulation Clock",
      &sim_clock, sim_clock, NULL);
}

